/*
 * Copyright (c) Tony Bybell 2005-2014.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 */

#include <config.h>
#include "globals.h"
#include <gtk/gtk.h>
#include "gtk23compat.h"
#include "symbol.h"
#include "translate.h"
#include "debug.h"

enum
{
    NAME_COLUMN,
    N_COLUMNS
};

static gboolean XXX_view_selection_func(GtkTreeSelection *selection,
                                        GtkTreeModel *model,
                                        GtkTreePath *path,
                                        gboolean path_currently_selected,
                                        gpointer userdata)
{
    (void)selection;
    (void)model;
    (void)userdata;

    gint *idx = NULL;

    if (path) {
        idx = gtk_tree_path_get_indices(path);

        if (idx) {
            if (!path_currently_selected) {
                GLOBALS->current_filter_translate_c_2 = idx[0] + 1;
            } else {
                GLOBALS->current_filter_translate_c_2 = 0; /* none */
            }
        }
    }

    return (TRUE);
}

/************************ splay ************************/

xl_Tree *xl_splay(char *i, xl_Tree *t)
{
    /* Simple top down splay, not requiring i to be in the tree t.  */
    /* What it does is described above.                             */
    xl_Tree N, *l, *r, *y;
    int dir;

    if (t == NULL)
        return t;
    N.left = N.right = NULL;
    l = r = &N;

    for (;;) {
        dir = strcasecmp(i, t->item);
        if (dir < 0) {
            if (t->left == NULL)
                break;
            if (strcasecmp(i, t->left->item) < 0) {
                y = t->left; /* rotate right */
                t->left = y->right;
                y->right = t;
                t = y;
                if (t->left == NULL)
                    break;
            }
            r->left = t; /* link right */
            r = t;
            t = t->left;
        } else if (dir > 0) {
            if (t->right == NULL)
                break;
            if (strcasecmp(i, t->right->item) > 0) {
                y = t->right; /* rotate left */
                t->right = y->left;
                y->left = t;
                t = y;
                if (t->right == NULL)
                    break;
            }
            l->right = t; /* link left */
            l = t;
            t = t->right;
        } else {
            break;
        }
    }
    l->right = t->left; /* assemble */
    r->left = t->right;
    t->left = N.right;
    t->right = N.left;
    return t;
}

xl_Tree *xl_insert(char *i, xl_Tree *t, char *trans)
{
    /* Insert i into the tree t, unless it's already there.    */
    /* Return a pointer to the resulting tree.                 */
    xl_Tree *n;
    int dir;

    n = (xl_Tree *)calloc_2(1, sizeof(xl_Tree));
    if (n == NULL) {
        fprintf(stderr, "xl_insert: ran out of memory, exiting.\n");
        exit(255);
    }
    n->item = strcpy(malloc_2(strlen(i) + 1), i);
    if (trans)
        n->trans = strcpy(malloc_2(strlen(trans) + 1), trans);

    if (t == NULL) {
        n->left = n->right = NULL;
        return n;
    }
    t = xl_splay(i, t);
    dir = strcasecmp(i, t->item);
    if (dir < 0) {
        n->left = t->left;
        n->right = t;
        t->left = NULL;
        return n;
    } else if (dir > 0) {
        n->right = t->right;
        n->left = t;
        t->right = NULL;
        return n;
    } else { /* We get here if it's already in the tree */
        /* Don't add it again                      */
        if (n->trans)
            free_2(n->trans);
        free_2(n->item);
        free_2(n);
        return t;
    }
}

xl_Tree *xl_delete(char *i, xl_Tree *t)
{
    /* Deletes i from the tree if it's there.               */
    /* Return a pointer to the resulting tree.              */
    xl_Tree *x;
    if (t == NULL)
        return NULL;
    t = xl_splay(i, t);
    if (strcmp(i, t->item) == 0) { /* found it */
        if (t->left == NULL) {
            x = t->right;
        } else {
            x = xl_splay(i, t->left);
            x->right = t->right;
        }
        if (t->trans)
            free_2(t->trans);
        free_2(t->item);
        free_2(t);
        return x;
    }
    return t; /* It wasn't there */
}

/************************ splay ************************/

void init_filetrans_data(void)
{
    int i;

    if (!GLOBALS->filesel_filter) {
        GLOBALS->filesel_filter = calloc_2(FILE_FILTER_MAX + 1, sizeof(char *));
    }
    if (!GLOBALS->xl_file_filter) {
        GLOBALS->xl_file_filter = calloc_2(FILE_FILTER_MAX + 1, sizeof(struct xl_tree_node *));
    }

    for (i = 0; i < FILE_FILTER_MAX + 1; i++) {
        GLOBALS->filesel_filter[i] = NULL;
        GLOBALS->xl_file_filter[i] = NULL;
    }
}

static void remove_file_filter_2(xl_Tree *t)
{
    if (t->left)
        remove_file_filter_2(t->left);
    if (t->right)
        remove_file_filter_2(t->right);
    if (t->item)
        free_2(t->item);
    if (t->trans)
        free_2(t->trans);
    free_2(t);
}

static void remove_file_filter(int which, int regen)
{
    if (GLOBALS->xl_file_filter[which]) {
        remove_file_filter_2(GLOBALS->xl_file_filter[which]);
        GLOBALS->xl_file_filter[which] = NULL;
    }

    if (regen) {
        GLOBALS->signalwindow_width_dirty = 1;
        redraw_signals_and_waves();
    }
}

static void load_file_filter(int which, const char *name)
{
    FILE *f = fopen(name, "rb");
    if (!f) {
        status_text("Could not open filter file!\n");
        return;
    }

    remove_file_filter(
        which,
        0); /* should never happen from GUI, but possible from save files or other weirdness */

    while (!feof(f)) {
        char *s = fgetmalloc(f);
        if (s) {
            char *lhs = s;

            while (*lhs && isspace((int)(unsigned char)*lhs))
                lhs++;
            if (lhs) {
                char *rhs = lhs;

                if (*lhs != '#') /* ignore comments */
                {
                    while (*rhs && !isspace((int)(unsigned char)*rhs))
                        rhs++;
                    if (*rhs) {
                        char *xlt = rhs + 1;
                        *rhs = 0;

                        while (*xlt && isspace((int)(unsigned char)*xlt))
                            xlt++;
                        if (*xlt) {
                            GLOBALS->xl_file_filter[which] =
                                xl_insert(lhs, GLOBALS->xl_file_filter[which], xlt);
                        }
                    }
                }
            }

            free_2(s);
        }
    }

    fclose(f);
}

static void load_enums_filter(int which, const char *name)
{
    int argc;
    char **spl = zSplitTclList(name, &argc);
    int i;

    if ((!spl) || (!argc) || (argc & 1)) {
        status_text("Malformed enums list!\n");
        return;
    }

    remove_file_filter(
        which,
        0); /* should never happen from GUI, but possible from save files or other weirdness */

    for (i = 0; i < argc; i += 2) {
        char *lhs = spl[i];
        char *xlt = spl[i + 1];

        GLOBALS->xl_file_filter[which] = xl_insert(lhs, GLOBALS->xl_file_filter[which], xlt);
    }
    free_2(spl);
}

int install_file_filter(int which)
{
    int found = 0;

    if ((which < 0) || (which >= (FILE_FILTER_MAX + 1))) {
        which = 0;
    }

    if (GLOBALS->traces.first) {
        Trptr t = GLOBALS->traces.first;
        while (t) {
            if (t->flags & TR_HIGHLIGHT) {
                if (!(t->flags & (TR_BLANK | TR_ANALOG_BLANK_STRETCH))) {
                    t->f_filter = which;
                    t->p_filter = 0;
                    if (!which) {
                        t->flags &= (~(TR_FTRANSLATED | TR_PTRANSLATED | TR_ANALOGMASK));
                    } else {
                        t->flags &= (~(TR_ANALOGMASK));
                        t->flags |= TR_FTRANSLATED;
                    }
                    found++;
                }
            }
            t = t->t_next;
        }
    }

    if (found) {
        GLOBALS->signalwindow_width_dirty = 1;
        redraw_signals_and_waves();
    }

    return (found);
}

/************************************************************************/

static void destroy_callback(GtkWidget *widget, GtkWidget *nothing)
{
    (void)widget;
    (void)nothing;

    GLOBALS->is_active_translate_c_5 = 0;
    gtk_widget_destroy(GLOBALS->window_translate_c_11);
    GLOBALS->window_translate_c_11 = NULL;
}

static void ok_callback(GtkWidget *widget, GtkWidget *nothing)
{
    install_file_filter(GLOBALS->current_filter_translate_c_2);
    destroy_callback(widget, nothing);
}

static void add_filter_callback_2(GtkWidget *widget, GtkWidget *nothing)
{
    (void)widget;
    (void)nothing;

    int i;

    if (!GLOBALS->filesel_ok) {
        return;
    }

    if (*GLOBALS->fileselbox_text) {
        for (i = 0; i < GLOBALS->num_file_filters; i++) {
            if (GLOBALS->filesel_filter[i]) {
                if (!strcmp(GLOBALS->filesel_filter[i], *GLOBALS->fileselbox_text)) {
                    status_text("Filter already imported.\n");
                    if (GLOBALS->is_active_translate_c_5)
                        gdk_window_raise(gtk_widget_get_window(GLOBALS->window_translate_c_11));
                    return;
                }
            }
        }
    }

    GLOBALS->num_file_filters++;
    load_file_filter(GLOBALS->num_file_filters, *GLOBALS->fileselbox_text);
    if (GLOBALS->xl_file_filter[GLOBALS->num_file_filters] &&
        (*GLOBALS->fileselbox_text /* scan-build */)) {
        if (GLOBALS->filesel_filter[GLOBALS->num_file_filters])
            free_2(GLOBALS->filesel_filter[GLOBALS->num_file_filters]);
        GLOBALS->filesel_filter[GLOBALS->num_file_filters] =
            malloc_2(strlen(*GLOBALS->fileselbox_text) + 1);
        strcpy(GLOBALS->filesel_filter[GLOBALS->num_file_filters], *GLOBALS->fileselbox_text);

        GtkTreeIter iter;
        gtk_list_store_append(GTK_LIST_STORE(GLOBALS->sig_store_translate), &iter);
        gtk_list_store_set(GTK_LIST_STORE(GLOBALS->sig_store_translate),
                           &iter,
                           NAME_COLUMN,
                           GLOBALS->filesel_filter[GLOBALS->num_file_filters],
                           -1);
    } else {
        GLOBALS->num_file_filters--;
    }

    if (GLOBALS->is_active_translate_c_5)
        gdk_window_raise(gtk_widget_get_window(GLOBALS->window_translate_c_11));
}

static void add_filter_callback(GtkWidget *widget, GtkWidget *nothing)
{
    (void)widget;
    (void)nothing;

    if (GLOBALS->num_file_filters == FILE_FILTER_MAX) {
        status_text("Max number of file filters installed already.\n");
        return;
    }

    fileselbox("Select Filter File",
               &GLOBALS->fcurr_translate_c_2,
               G_CALLBACK(add_filter_callback_2),
               G_CALLBACK(NULL),
               NULL,
               0);
}

/*
 * mainline..
 */
void trans_searchbox(const char *title)
{
    int i;

    const gchar *titles[] = {"Filter Select"};

    if (GLOBALS->is_active_translate_c_5) {
        gdk_window_raise(gtk_widget_get_window(GLOBALS->window_translate_c_11));
        return;
    }

    GLOBALS->is_active_translate_c_5 = 1;
    GLOBALS->current_filter_translate_c_2 = 0;

    /* create a new modal window */
    GLOBALS->window_translate_c_11 =
        gtk_window_new(GLOBALS->disable_window_manager ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
    install_focus_cb(GLOBALS->window_translate_c_11,
                     ((char *)&GLOBALS->window_translate_c_11) - ((char *)GLOBALS));

    gtk_window_set_title(GTK_WINDOW(GLOBALS->window_translate_c_11), title);
    gtkwave_signal_connect(GLOBALS->window_translate_c_11,
                           "delete_event",
                           (GCallback)destroy_callback,
                           NULL);
    gtk_container_set_border_width(GTK_CONTAINER(GLOBALS->window_translate_c_11), 12);

    GtkWidget *main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
    gtk_container_add(GTK_CONTAINER(GLOBALS->window_translate_c_11), main_vbox);

    GLOBALS->sig_store_translate = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);
    GtkWidget *sig_view =
        gtk_tree_view_new_with_model(GTK_TREE_MODEL(GLOBALS->sig_store_translate));

    /* The view now holds a reference.  We can get rid of our own reference */
    g_object_unref(G_OBJECT(GLOBALS->sig_store_translate));

    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    GtkTreeViewColumn *column =
        gtk_tree_view_column_new_with_attributes(titles[0], renderer, "text", NAME_COLUMN, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(sig_view), column);

    /* Setup the selection handler */
    GLOBALS->sig_selection_translate = gtk_tree_view_get_selection(GTK_TREE_VIEW(sig_view));
    gtk_tree_selection_set_mode(GLOBALS->sig_selection_translate, GTK_SELECTION_SINGLE);
    gtk_tree_selection_set_select_function(GLOBALS->sig_selection_translate,
                                           XXX_view_selection_func,
                                           NULL,
                                           NULL);

    gtk_list_store_clear(GTK_LIST_STORE(GLOBALS->sig_store_translate));
    for (i = 0; i < GLOBALS->num_file_filters; i++) {
        GtkTreeIter iter;
        gtk_list_store_append(GTK_LIST_STORE(GLOBALS->sig_store_translate), &iter);
        gtk_list_store_set(GTK_LIST_STORE(GLOBALS->sig_store_translate),
                           &iter,
                           NAME_COLUMN,
                           GLOBALS->filesel_filter[i + 1],
                           -1);
    }

    GtkWidget *scrolled_win = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_win), GTK_SHADOW_IN);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win),
                                   GTK_POLICY_AUTOMATIC,
                                   GTK_POLICY_AUTOMATIC);
    gtk_widget_set_size_request(GTK_WIDGET(scrolled_win), -1, 300);
    gtk_container_add(GTK_CONTAINER(scrolled_win), sig_view);

    gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_win, TRUE, TRUE, 0);

    GtkWidget *add_button = gtk_button_new_with_label("Add Filter to List");
    gtkwave_signal_connect_object(add_button,
                                  "clicked",
                                  G_CALLBACK(add_filter_callback),
                                  GLOBALS->window_translate_c_11);
    gtk_tooltips_set_tip_2(
        add_button,
        "Bring up a file requester to add a filter to the filter select window.");
    gtk_box_pack_start(GTK_BOX(main_vbox), add_button, FALSE, FALSE, 0);

    GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
    gtk_box_set_homogeneous(GTK_BOX(button_box), TRUE);
    gtk_box_pack_start(GTK_BOX(main_vbox), button_box, FALSE, FALSE, 0);

    GtkWidget *ok_button = gtk_button_new_with_label("OK");
    gtkwave_signal_connect_object(ok_button,
                                  "clicked",
                                  G_CALLBACK(ok_callback),
                                  GLOBALS->window_translate_c_11);
    gtk_tooltips_set_tip_2(ok_button,
                           "Add selected signals to end of the display on the main window.");
    gtk_box_pack_start(GTK_BOX(button_box), ok_button, TRUE, TRUE, 0);

    GtkWidget *cancel_button = gtk_button_new_with_label("Cancel");
    gtkwave_signal_connect_object(cancel_button,
                                  "clicked",
                                  G_CALLBACK(destroy_callback),
                                  GLOBALS->window_translate_c_11);
    gtk_tooltips_set_tip_2(cancel_button, "Do nothing and return to the main window.");
    gtk_box_pack_start(GTK_BOX(button_box), cancel_button, TRUE, TRUE, 0);

    gtk_widget_set_size_request(GTK_WIDGET(GLOBALS->window_translate_c_11), 400, 400);
    gtk_widget_show_all(GLOBALS->window_translate_c_11);
}

/*
 * currently only called by parsewavline+tcl
 */
static void set_current_translate_generic(const char *name, int typ)
{
    int i;

    if (typ) {
        for (i = 1; i < GLOBALS->num_file_filters + 1; i++) {
            if (!strcmp(GLOBALS->filesel_filter[i], name)) {
                GLOBALS->current_translate_file = i;
                return;
            }
        }

        if (!strcmp(WAVE_TCL_INSTALLED_FILTER, name)) {
            GLOBALS->current_translate_file = 0;
            return;
        }
    }

    if (GLOBALS->num_file_filters < FILE_FILTER_MAX) {
        GLOBALS->num_file_filters++;

        if (typ) {
            load_file_filter(GLOBALS->num_file_filters, name);
        } else {
            load_enums_filter(GLOBALS->num_file_filters, name);
        }

        if (!GLOBALS->xl_file_filter[GLOBALS->num_file_filters]) {
            GLOBALS->num_file_filters--;
            GLOBALS->current_translate_file = 0;
        } else {
            if (GLOBALS->filesel_filter[GLOBALS->num_file_filters])
                free_2(GLOBALS->filesel_filter[GLOBALS->num_file_filters]);

            if (!typ) {
                name = WAVE_TCL_INSTALLED_FILTER;
            }

            GLOBALS->filesel_filter[GLOBALS->num_file_filters] = malloc_2(strlen(name) + 1);
            strcpy(GLOBALS->filesel_filter[GLOBALS->num_file_filters], name);
            GLOBALS->current_translate_file = GLOBALS->num_file_filters;
        }
    }
}

void set_current_translate_file(char *name)
{
    set_current_translate_generic(name, 1); /* use file, not enums */
}

void set_current_translate_enums(char *lst)
{
    set_current_translate_generic(lst, 0); /* use enums */
}
